/*
 * Objekt.cpp
 *
 *  Created on: Feb 11, 2011
 *      Author: alki
 */

#include "Objekt.h"
#include "../math/Gerade.h"
#include "../math/Ebene.h"
#include <stdlib.h>
#include <iostream>
#include "../Exception.h"

using namespace std;

Objekt::Objekt() {
	this->m_triangles.clear();
	this->m_vertices.clear();
	this->m_matman=NULL;
	this->m_texturen.clear();
}

Objekt::~Objekt() {
	this->m_triangles.clear();
	this->m_vertices.clear();
	this->m_matman=NULL;
	this->m_texturen.clear();
}

void Objekt::CalcVNormals() {
	for(unsigned int v=0;v<this->m_vertices.size();v++) {
		Vektor n;
		for(unsigned int t=0;t<this->m_triangles.size();t++) {
			bool dabei = false;
			for(unsigned int i=0;i<3;i++) {
				if(this->m_triangles[t].points[i]==v)
					dabei = true;
			}
			if(dabei) {
				Vektor &a=this->m_vertices[this->m_triangles[t].points[0]].pos;
				Vektor &b=this->m_vertices[this->m_triangles[t].points[1]].pos;
				Vektor &c=this->m_vertices[this->m_triangles[t].points[2]].pos;
				Vektor temp = (b-a).CrossProduct(c-a);
				n=n+temp;
			}
		}
		n.Normalize();
		this->m_vertices[v].n=n;
	}
}

void Objekt::CalcRadius() {
	float maxrad=0;
	for(unsigned int v=0;v<this->m_vertices.size();v++) {
		float l=this->m_vertices[v].pos.Length();
		maxrad = (l<maxrad)?maxrad:l;
	}
	this->m_radius=maxrad;
}

void Objekt::CalcPosition() {
	Vektor mid;

	for(unsigned int i=0;i<this->m_vertices.size();i++) {
		mid=mid+(this->m_vertices[i].pos/this->m_vertices.size());
	}

	this->m_position=this->m_position+mid;

	for(unsigned int i=0;i<this->m_vertices.size();i++) {
		this->m_vertices[i].pos=this->m_vertices[i].pos-mid;
	}
}

bool Objekt::Intersects(const Gerade &ray,Vektor *schnittpunkt,float *rret,float *sret) const {
	Vektor pos=this->getPosition();
	Vektor a=ray.getPos();
	Vektor r=ray.getDir();

	float d=r.CrossProduct(pos-a).Length()/r.Length();
	if(d>this->getRadius()) return false;

	for(int i=0;i<this->getTriangleCount();i++) {
		TRIANGLE t=this->getTriangle(i);
		float rotX = this->getRotX();
		float rotY = this->getRotY();
		float rotZ = this->getRotZ();

		Vektor a(this->getVertex(t.points[0]).pos.getX(),this->getVertex(t.points[0]).pos.getY(),this->getVertex(t.points[0]).pos.getZ());
		Vektor b(this->getVertex(t.points[1]).pos.getX(),this->getVertex(t.points[1]).pos.getY(),this->getVertex(t.points[1]).pos.getZ());
		Vektor c(this->getVertex(t.points[2]).pos.getX(),this->getVertex(t.points[2]).pos.getY(),this->getVertex(t.points[2]).pos.getZ());
		a.Rotate(rotX,rotY,rotZ);
		b.Rotate(rotX,rotY,rotZ);
		c.Rotate(rotX,rotY,rotZ);
		a.Translate(pos);
		b.Translate(pos);
		c.Translate(pos);
		Ebene e;
		e.setPos(a);
		e.setDir(b-a,c-a);
		float r,s;
		if(ray.Intersects_E(e,schnittpunkt,&r,&s)) {
			if(r>=-0.0001 && s>=0.0001 && r+s<=1.0002) {
				if(rret!=NULL) *rret=r;
				if(sret!=NULL) *sret=s;

				return true;
			}
		}
	}
	return false;
}

Vektor Objekt::getNormal(const Gerade &ray,const Vektor &pos) const {
	Vektor S(pos);
	S.Translate(-this->m_position.getX(),-this->m_position.getY(),-this->m_position.getZ());
	S.Rotate(-this->m_rotX,-this->m_rotY,-this->m_rotZ,true);
	float* rs=new float[2];

	int t=this->getTri(ray,S,&rs[0],&rs[1]);
	if(t==-1) {
		Vektor n;
		return n;
		throw new Exception(NULL,__FILE__,__LINE__,"Fehler:","Kein passendes Dreieck für Strahl gefunden!");
	}

	TRIANGLE tri=this->m_triangles[t];
	Vektor a=this->m_vertices[tri.points[0]].n;
	Vektor b=this->m_vertices[tri.points[1]].n;
	Vektor c=this->m_vertices[tri.points[2]].n;

	Vektor n=a*(1-rs[0]-rs[1])+(b*(rs[0]))+(c*(rs[1]));
	delete rs;
	n.Normalize();
	n.Rotate(this->m_rotX,this->m_rotY,this->m_rotZ);

	return n;
}

int Objekt::getTri(const Gerade &ray,const Vektor &pos,float *r,float *s) const {
	float l[this->m_triangles.size()];
	float lmin=1000;
	for(unsigned int i=0;i<this->m_triangles.size();i++) {
		Vektor a=this->m_vertices[this->m_triangles[i].points[0]].pos;
		Vektor b=this->m_vertices[this->m_triangles[i].points[1]].pos;
		Vektor c=this->m_vertices[this->m_triangles[i].points[2]].pos;

		Ebene e;
		e.setPos(a);
		e.setDir(b-a,c-a);
		Vektor S;
		if(ray.Intersects_E(e,&S,r,s)) {
			l[i]=(S-pos).Length();
			if(l[i]<lmin) lmin=l[i];
		}
		else {
			l[i]=-1;
		}
	}

	for(unsigned int i=0;i<this->m_triangles.size();i++) {
		if(l[i]==lmin) {
			Vektor a=this->m_vertices[this->m_triangles[i].points[0]].pos;
			Vektor b=this->m_vertices[this->m_triangles[i].points[1]].pos;
			Vektor c=this->m_vertices[this->m_triangles[i].points[2]].pos;

			Vektor n=(c-a).CrossProduct(b-a);
			Gerade g;
			g.setPos(pos);
			g.setDir(n);

			Ebene e;
			e.setPos(a);
			e.setDir(b-a,c-a);
			g.Intersects_E(e,NULL,r,s);

			return i;
		}
	}
	return -1;
}

int Objekt::getTri(const Vektor &pos,float *r,float *s) const {
	float l[this->m_triangles.size()];
	float lmin=1000;
	for(unsigned int i=0;i<this->m_triangles.size();i++) {
		Vektor a=this->m_vertices[this->m_triangles[i].points[0]].pos;
		Vektor b=this->m_vertices[this->m_triangles[i].points[1]].pos;
		Vektor c=this->m_vertices[this->m_triangles[i].points[2]].pos;

		Vektor n=(c-a).CrossProduct(b-a);
		Gerade g;
		g.setPos(pos);
		g.setDir(n);

		Ebene e;
		e.setPos(a);
		e.setDir(b-a,c-a);
		Vektor S;
		if(g.Intersects_E(e,&S,r,s)) {
			if(*r<-0.01 || *s<-0.01 || *r+*s>1.01) {
				l[i]=-1;
				continue;
			}

			l[i]=(S-pos).Length();
			if(l[i]<lmin) lmin=l[i];
		}
		else {
			l[i]=-1;
		}
	}

	for(unsigned int i=0;i<this->m_triangles.size();i++) {
		if(l[i]==lmin) {
			Vektor a=this->m_vertices[this->m_triangles[i].points[0]].pos;
			Vektor b=this->m_vertices[this->m_triangles[i].points[1]].pos;
			Vektor c=this->m_vertices[this->m_triangles[i].points[2]].pos;

			Vektor n=(c-a).CrossProduct(b-a);
			Gerade g;
			g.setPos(pos);
			g.setDir(n);

			Ebene e;
			e.setPos(a);
			e.setDir(b-a,c-a);
			g.Intersects_E(e,NULL,r,s);

			return i;
		}
	}
	return -1;
}

Material Objekt::getMaterial(const Gerade &ray,const Vektor &pos,const MaterialManager &matman) const {
	Vektor S(pos);
	S.Translate(-this->m_position.getX(),-this->m_position.getY(),-this->m_position.getZ());
	S.Rotate(-this->m_rotX,-this->m_rotY,-this->m_rotZ,true);
	float r;
	float s;

	int t=this->getTri(ray,S,&r,&s);
	if(t==-1) {
		Material m;
		return m;
		cout << r << "|" << s << endl;
		pos.out();
		ray.getDir().out();
		ray.getPos().out();
		throw new Exception(NULL,__FILE__,__LINE__,"Fehler:","kein passendes Dreieck für Strahl gefunden!","Objekt::getMaterial()");
	}

	TRIANGLE tri=this->m_triangles[t];
	if(this->m_vertices[tri.points[0]].mat==-1||this->m_vertices[tri.points[1]].mat==-1||this->m_vertices[tri.points[2]].mat==-1) {
		Material n;
		n.setLightEmissive(FARBE(1,1,1));
		return n;
	}

	const Material *a=matman.getMaterial(this->m_vertices[tri.points[0]].mat);
	const Material *b=matman.getMaterial(this->m_vertices[tri.points[1]].mat);
	const Material *c=matman.getMaterial(this->m_vertices[tri.points[2]].mat);

	Material m;

//	m=Material(*a);
//	return m;

	FARBE e=a->getLightEmissive()*(1.0f-r-s)+b->getLightEmissive()*r+c->getLightEmissive()*s;
	FARBE d=a->getLightDiffus()*(1.0f-r-s)+b->getLightDiffus()*r+c->getLightDiffus()*s;
	FARBE sp=a->getLightSpecular()*(1.0f-r-s)+b->getLightSpecular()*r+c->getLightSpecular()*s;
	float ref=a->getReflexion()*(1.0f-r-s)+b->getReflexion()*r+c->getReflexion()*s;
	float dif=a->getDiffusFaktor()*(1.0f-r-s)+b->getDiffusFaktor()*r+c->getDiffusFaktor()*s;
	float spec=a->getSpecularFaktor()*(1.0f-r-s)+b->getSpecularFaktor()*r+c->getSpecularFaktor()*s;
	m.setLightEmissive(e);
	m.setLightDiffus(d);
	m.setLightSpecular(sp);
	m.setReflexion(ref);
	m.setDiffusFaktor(dif);
	m.setSpecularFaktor(spec);
	if(a->getTextur()!=NULL) {
		m.setTextur(a->getTextur());
		float u1,u2,u3,v1,v2,v3;
		u1=this->m_vertices[tri.points[0]].u;
		v1=this->m_vertices[tri.points[0]].v;
		u2=this->m_vertices[tri.points[1]].u;
		v2=this->m_vertices[tri.points[1]].v;
		u3=this->m_vertices[tri.points[2]].u;
		v3=this->m_vertices[tri.points[2]].v;
		float u=u1*(1.0f-r-s)+u2*r+u3*s;
		float v=v1*(1.0f-r-s)+v2*r+v3*s;
		m.setTexCoords(u,v);
	}

	return m;
}

bool Objekt::leuchtet() const {
	for(unsigned int i=0;i<this->m_vertices.size();i++) {
		if(!this->m_matman->getMaterial(this->m_vertices[i].mat)->getLightEmissive().DARK())
			return true;
	}
	return false;
}

bool Objekt::Point(Vektor p) const {
	Vektor S(p);
	S.Translate(-this->m_position.getX(),-this->m_position.getY(),-this->m_position.getZ());
	S.Rotate(-this->m_rotX,-this->m_rotY,-this->m_rotZ,true);

	float r,s;
	if(this->getTri(S,&r,&s)!=-1) {
		return true;
	}
	return false;
}

Vektor Objekt::getMaxRad() const {
	if(this->m_vertices.size()==0)
		return Vektor(0,0,0);

	float radx=0;
	float rady=0;
	float radz=0;

	for(unsigned int i=0;i<this->m_vertices.size();i++) {
		float temp;

		temp=fabs(this->m_vertices[i].pos.getX());
		radx=(radx>temp)?radx:temp;
		temp=fabs(this->m_vertices[i].pos.getY());
		rady=(rady>temp)?rady:temp;
		temp=fabs(this->m_vertices[i].pos.getZ());
		radz=(radz>temp)?radz:temp;
	}

	return Vektor(radx,rady,radz);
}

Vektor Objekt::getMin() const {
	Vektor temp=this->m_vertices[0].pos;

	temp.Rotate(this->m_rotX,this->m_rotY,this->m_rotZ,false);
	temp.Translate(this->m_position.getX(),this->m_position.getY(),this->m_position.getZ());
	Vektor min=temp;

	for(unsigned int i=1;i<this->m_vertices.size();i++) {
		Vektor temp=this->m_vertices[i].pos;
		temp.Rotate(this->m_rotX,this->m_rotY,this->m_rotZ,false);
		temp.Translate(this->m_position.getX(),this->m_position.getY(),this->m_position.getZ());

		if(min.getX()>temp.getX())
			min.setX(temp.getX());
		if(min.getY()>temp.getY())
			min.setY(temp.getY());
		if(min.getZ()>temp.getZ())
			min.setZ(temp.getZ());
	}

	return min;
}

Vektor Objekt::getMax() const {
	Vektor temp=this->m_vertices[0].pos;

	temp.Rotate(this->m_rotX,this->m_rotY,this->m_rotZ);
	temp.Translate(this->m_position.getX(),this->m_position.getY(),this->m_position.getZ());
	Vektor max=temp;

	for(unsigned int i=1;i<this->m_vertices.size();i++) {
		temp=this->m_vertices[i].pos;
		temp.Rotate(this->m_rotX,this->m_rotY,this->m_rotZ);
		temp.Translate(this->m_position.getX(),this->m_position.getY(),this->m_position.getZ());

		if(max.getX()<temp.getX())
			max.setX(temp.getX());
		if(max.getY()<temp.getY())
			max.setY(temp.getY());
		if(max.getZ()<temp.getZ())
			max.setZ(temp.getZ());
	}

	return max;
}

void Objekt::AddVertices(vector<VERTEX> verts) {
	for(unsigned int i=0;i<verts.size();i++) {
		this->m_vertices.push_back(verts[i]);
	}
}

void Objekt::AddVertex(VERTEX vert) {
	this->m_vertices.push_back(vert);
}

void Objekt::AddTriangle(TRIANGLE tri) {
	this->m_triangles.push_back(tri);
}
